﻿using Exiled.API.Features;
using InventorySystem;
using InventorySystem.Items;
using InventorySystem.Items.Firearms;
using InventorySystem.Items.Pickups;
using MapGeneration.Distributors;
using MEC;
using RgCore.GameModes;
using RgCore.PlayerHud;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using CustomItem.Interfaces;
using UnityEngine;
using Room = Exiled.API.Features.Room;

namespace CustomItem
{
    public abstract class CustomItem
    {
        public static List<CustomItem> AllItems { get; set; } = new List<CustomItem>();
        public static CoroutineHandle Coroutine;

        public CustomItem(ushort serial, bool active = true, ItemBase ib = null, ItemPickupBase ipb = null)
        {
            Serial = serial;
            Base = ib;
            PickupBase = ipb;
            if (active)
            {
                Item_Onactive();
            }
        }

        public static CustomItem GetItem(ushort serial) => AllItems.FirstOrDefault(x => x.Serial == serial);

        public static string[] HudInfo(Player player)
        {
            if (player.CurrentItem == null)
            {
                return Array.Empty<string>();
            }
            var item = GetItem(player.CurrentItem.Serial);
            if (item == null)
            {
                return Array.Empty<string>();
            }
            string result = $"正在持有: [<color=#AAFFAA>{item.Attribute.Name}</color>]";
            var list_ = item.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
                .Where(x => x.GetCustomAttribute<DescriptionAttribute>() != null);
            if (item is IHasCooldown ihc)
            {
                result += $" | {ihc.Cooldown.GetText(list_.Any())}";
            }
            foreach (var prop in list_)
            {
                var key = prop.GetCustomAttribute<DescriptionAttribute>().Description;
                var value = prop.GetValue(item);
                result += $" | {key}: <color=yellow>{(value is bool b ? (b ? "是" : "否") : value)}</color>";
            }
            return new string[] { result };
        }

        public ushort Serial { get; set; }
        public ItemInfoAttribute Attribute => GetType().GetCustomAttribute<ItemInfoAttribute>();

        public ItemBase Base { get; private set; }

        public ItemPickupBase PickupBase { get; private set; }

        public virtual void Item_Onactive()
        {
        }

        public virtual void Item_Ondeactive()
        {
        }

        public void DestroySelf(bool includeObject)
        {
            Item_Ondeactive();
            AllItems.Remove(this);
            if (includeObject)
            {
                Base.OwnerInventory.ServerRemoveItem(Serial, PickupBase);
            }
        }

        private static ItemPickupBase CreatePickup(ItemType id, Vector3 position, float weight = 1f)
        {
            if (!InventoryItemLoader.AvailableItems.TryGetValue(id, out ItemBase itemBase))
                return null;
            ushort serial = ItemSerialGenerator.GenerateNext();
            ItemPickupBase ipb = UnityEngine.Object.Instantiate(itemBase.PickupDropModel, position, Quaternion.identity);
            ipb.Info.Serial = serial;
            ipb.Info.ItemId = id;
            ipb.Info.WeightKg = itemBase.Weight * weight;
            return ipb;
        }

        private static ItemPickupBase CreatePickup(ItemType id, Transform transform, float weight = 1f)
        {
            if (!InventoryItemLoader.AvailableItems.TryGetValue(id, out ItemBase itemBase))
                return null;
            ushort serial = ItemSerialGenerator.GenerateNext();
            ItemPickupBase ipb = UnityEngine.Object.Instantiate(itemBase.PickupDropModel, transform.position, transform.rotation);
            ipb.Info.Serial = serial;
            ipb.Info.ItemId = id;
            ipb.Info.WeightKg = itemBase.Weight * weight;
            ipb.transform.SetParent(transform);
            return ipb;
        }

        private static void SpawnInChamber(Type type, LockerChamber lc, int count = 1)
        {
            Log.Info("生成：" + type.Name);
            ItemInfoAttribute attribute = type.GetCustomAttribute<ItemInfoAttribute>();
            for (int i = 0; i < count; i++)
            {
                var ipb = CreatePickup(attribute.Item, lc._spawnpoint);
                if (lc._spawnOnFirstChamberOpening)
                    lc._toBeSpawned.Add(ipb);
                else
                    ItemDistributor.SpawnPickup(ipb);
                CustomItem pi = (CustomItem)Activator.CreateInstance(type, new object[] { ipb.Info.Serial, true, null, ipb });
                AllItems.Add(pi);
            }
        }

        private static void SpawnInRoom(Type type, Room room, int count = 1, float offset = 2f)
        {
            ItemInfoAttribute attribute = type.GetCustomAttribute<ItemInfoAttribute>();
            Vector3 position = room.Transform.position;
            var id = UnityEngine.Object.FindObjectOfType<ItemDistributor>();
            position.x += UnityEngine.Random.Range(-offset, offset);
            position.z += UnityEngine.Random.Range(-offset, offset);
            for (int i = 0; i < count; i++)
            {
                var ipb = CreatePickup(attribute.Item, position);
                var door = room.Doors.FirstOrDefault();
                if (door != null)
                    id.RegisterUnspawnedObject(room.Doors.FirstOrDefault().Base, ipb.gameObject);
                else
                    ItemDistributor.SpawnPickup(ipb);
                CustomItem pi = (CustomItem)Activator.CreateInstance(type, new object[] { ipb.Info.Serial, true, null, ipb });
                if (pi is IWeaponItem iwi)
                {
                    (ipb as FirearmPickup).Status = new FirearmStatus((ipb as FirearmPickup).Status.Ammo, FirearmStatusFlags.MagazineInserted, iwi.Attachments);
                }
                AllItems.Add(pi);
            }
        }

        public static void Spawn(Type type, Vector3 position)
        {
            ItemInfoAttribute attribute = type.GetCustomAttribute<ItemInfoAttribute>();
            if (attribute == null)
            {
                Log.Error("【自定义物品】Type类型不正确");
                return;
            }
            var ipb = CreatePickup(attribute.Item, position);
            CustomItem pi = (CustomItem)Activator.CreateInstance(type, new object[] {ipb.Info.Serial, true, null, ipb });
            if (pi is IWeaponItem iwi)
            {
                (ipb as FirearmPickup).Status = new FirearmStatus((ipb as FirearmPickup).Status.Ammo, FirearmStatusFlags.MagazineInserted, iwi.Attachments);
            }
            AllItems.Add(pi);
            ItemDistributor.SpawnPickup(ipb);
        }

        public static void Give(Player player, Type type)
        {
            ItemInfoAttribute attribute = type.GetCustomAttribute<ItemInfoAttribute>();
            if (attribute == null)
            {
                Log.Error("【自定义物品】Type类型不正确");
                return;
            }
            var serial = ItemSerialGenerator.GenerateNext();
            var itembase = player.Inventory.ServerAddItem(attribute.Item, serial);
            CustomItem pi = (CustomItem)Activator.CreateInstance(type, new object[] { serial, true, itembase, null });
            if (pi is IWeaponItem iwi)
            {
                (itembase as Firearm).Status = new FirearmStatus((itembase as Firearm).Status.Ammo, FirearmStatusFlags.MagazineInserted, iwi.Attachments);
            }
            AllItems.Add(pi);
        }

        public static void SetEnabled(bool enable)
        {
            if (enable)
            {
                RgCore.RgCore.RoundStarted += OnRoundStarted;
                Exiled.Events.Handlers.Player.ChangingItem += Player_ChangingItem;
                ItemPickupBase.OnPickupAdded += ItemPickupBase_OnPickupAdded;
                ItemPickupBase.OnPickupDestroyed += ItemPickupBase_OnPickupDestroyed;
                InventoryExtensions.OnItemAdded += InventoryExtensions_OnItemAdded;
                InventoryExtensions.OnItemRemoved += InventoryExtensions_OnItemRemoved;
                Coroutine = Timing.RunCoroutine(Update());
            }
            else
            {
                foreach (var item in AllItems)
                {
                    item.Item_Ondeactive();
                }
                AllItems.Clear();
                RgCore.RgCore.RoundStarted -= OnRoundStarted;
                Exiled.Events.Handlers.Player.ChangingItem -= Player_ChangingItem;
                ItemPickupBase.OnPickupAdded -= ItemPickupBase_OnPickupAdded;
                ItemPickupBase.OnPickupDestroyed -= ItemPickupBase_OnPickupDestroyed;
                InventoryExtensions.OnItemAdded -= InventoryExtensions_OnItemAdded;
                InventoryExtensions.OnItemRemoved -= InventoryExtensions_OnItemRemoved;
                Timing.KillCoroutines(Coroutine);
            }
        }

        private static void InventoryExtensions_OnItemRemoved(ReferenceHub arg1, ItemBase arg2, ItemPickupBase arg3)
        {
            if (arg2.ItemSerial == 0)
                return;
            var item = GetItem(arg2.ItemSerial);
            if (item != null)
            {
                item.Base = null;
            }
        }

        private static void InventoryExtensions_OnItemAdded(ReferenceHub arg1, ItemBase arg2, ItemPickupBase arg3)
        {
            if (arg2.ItemSerial == 0)
                return;
            var item = GetItem(arg2.ItemSerial);
            if (item != null)
            {
                item.Base = arg2;
            }
        }

        private static void ItemPickupBase_OnPickupDestroyed(ItemPickupBase obj)
        {
            if (obj.NetworkInfo.Serial == 0)
                return;
            var item = GetItem(obj.NetworkInfo.Serial);
            if (item != null)
            {
                item.PickupBase = null;
            }
        }

        private static void ItemPickupBase_OnPickupAdded(ItemPickupBase obj)
        {
            if (obj.NetworkInfo.Serial == 0)
                return;
            var item = GetItem(obj.NetworkInfo.Serial);
            if (item != null)
            {
                item.PickupBase = obj;
            }
        }

        private static IEnumerator<float> Update()
        {
            while (true)
            {
                try
                {
                    var itemlist = AllItems.Where(x => x.PickupBase != null);
                    foreach (Player player in Player.List)
                    {
                        CustomItem item = null;
                        float minDist = float.MaxValue;
                        var visuals = Physics.RaycastAll(player.CameraTransform.position, player.CameraTransform.forward, 3f);
                        foreach (var x in itemlist)
                        {
                            if (visuals.Any(y => y.collider.attachedRigidbody == (x.PickupBase.PhysicsModule as PickupStandardPhysics).Rb))
                            {
                                var dist = Vector3.Distance(player.Position, x.PickupBase.Position);
                                if (dist < minDist)
                                {
                                    minDist = dist;
                                    item = x;
                                }
                            }
                        }
                        if (item != null)
                        {
                            if (!player.HasMsg("item_hint"))
                                player.SendRawMsg("item_hint", $"[<color=#00FF7F>{item.Attribute.Name}</color>]", 2, ScreenLocation.Center, true);
                        }
                        else
                        {
                            if (player.HasMsg("item_hint"))
                            {
                                player.DeleteMsg("item_hint", true);
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    Log.Error(e);
                }
                yield return Timing.WaitForSeconds(0.1f);
            }
        }

        private static void OnRoundStarted()
        {
            if (RgCore.RgCore.instance.Config.GameModeEnabled && !(GameMode.CurrentGameMode is Normal))
            {
                return;
            }
            Log.Info("开始生成自定义物品");
            Dictionary<Type, SpawnRule> tospawn = new Dictionary<Type, SpawnRule>();
            List<ICustomSpawn> customSpawn = new List<ICustomSpawn>();
            foreach (var item in CustomItemPlugin.Instance.Manager.AllTypes.Keys)
            {
                if (item.BaseType == typeof(CustomItem))
                {
                    var inst = (CustomItem)Activator.CreateInstance(item, new object[] { (ushort)0, false, null, null});
                    if (inst is IRoundStartSpawn irss)
                    {
                        SpawnRule sr = irss.Rule;
                        if (UnityEngine.Random.Range(0, 1f) < sr.SpawnChance)
                        {
                            tospawn.Add(item, sr);
                        }
                    }
                    else if (inst is ICustomSpawn ics && ics.SpawnWhenRoundStarts)
                    {
                        customSpawn.Add(ics);
                        continue;
                    }
                }
            }
            Log.Info($"有 {tospawn.Count} 种自然生成项目，{customSpawn.Count} 种自定义生成项目");
            List<object> availables = new List<object>();
            availables.AddRange(Room.List);
            availables.AddRange(UnityEngine.Object.FindObjectsOfType<Locker>());
            availables.RemoveAll(x => x == null);
            Log.Info($"有 {availables.Count} 个可用生成点");
            foreach (var keys in tospawn)
            {
                var rule = keys.Value;
                List<object> list = new List<object>(availables);
                list.RemoveAll(x => x is Room r && !rule.SpawnableRooms.Contains(r.RoomName));
                list.RemoveAll(x => x is Locker l && !rule.SpawnableStructures.Contains(l.GetStructureType()));
                int remain = rule.SpawnCount + UnityEngine.Random.Range(-rule.Offset, rule.Offset + 1);
                while (remain > 0)
                {
                    object obj = list[UnityEngine.Random.Range(0, list.Count)];
                    int count = rule.StackSpawn ? UnityEngine.Random.Range(1, Math.Min(remain, rule.MaxStacks) + 1) : 1;
                    if (obj is Room room)
                    {
                        SpawnInRoom(keys.Key, room, count, 3f);
                    }
                    else if (obj is Locker l)
                    {
                        var chamber = l.Chambers[UnityEngine.Random.Range(0, l.Chambers.Count())];
                        SpawnInChamber(keys.Key, chamber, count);
                    }
                    remain -= count;
                }
            }
            foreach (var c in customSpawn)
            {
                Log.Info($"开始自定义生成 {c.GetType().Name}");
                c.CustomSpawn();
            }
            Log.Info("已全部生成完毕");
        }

        private static void Player_ChangingItem(Exiled.Events.EventArgs.Player.ChangingItemEventArgs ev)
        {
            if (ev.Item != null)
            {
                var item = GetItem(ev.Item.Serial);
                if (item != null)
                {
                    ev.Player.DeleteMsg("item_info", false);
                    ev.Player.SendRawMsg("item_info", HudInfo, 0, ScreenLocation.Bottom, true);
                    return;
                }
            }
            ev.Player.DeleteMsg("item_info", true);
        }
    }
}